package com.ruoyi.business.dbopt.service;

import com.alibaba.fastjson.JSON;
import com.ruoyi.business.dbopt.utils.CommonException;
import com.ruoyi.business.dbopt.utils.FileUtil;
import com.ruoyi.business.dbopt.utils.MailUtil;
import com.ruoyi.business.dbopt.utils.StringPool;
import com.ruoyi.business.dbopt.vo.BackupFileInfo;
import com.ruoyi.business.dbopt.vo.OptCommand;
import com.ruoyi.business.dbopt.vo.OptLog;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class DbOptService {

    private final MailUtil mailUtil;

    @Value("${config.back_path}")
    private String backPath;

    @Value("${config.log_item:2}")
    private Integer logItem;

    private static boolean isLinux = System.getProperty("os.name").startsWith("Linux");

    /**
     * 执行备份
     */
    public void backup(OptCommand optCommand) {
        String dbName = optCommand.getDbName();
        String to = optCommand.getMailTo();

        String fileName = String.format("%s.%s.sql", dbName, LocalDate.now());
        String filePath = String.format("%sfiles%s%s", backPath, File.separator, fileName);
        try {
            log.info("开始备份 {}", filePath);
            // 执行备份文件
            String execLog = this.exec(1, dbName, filePath);
            this.writeLog(backPath, dbName, fileName, execLog);
            log.info("备份完成 {}", filePath);
        } catch (IOException | InterruptedException e) {
            throw new CommonException("备份命令执行失败! " + e.getMessage());
        }

        if (!StringUtils.isEmpty(to)) {
            // 发送邮箱
            mailUtil.sendBackFile(filePath, to);
        }
    }

    private String exec(Integer cmd, String dbName, String filePath) throws IOException, InterruptedException {
        Runtime runtime = Runtime.getRuntime();

        String exeFileSuffix = isLinux ? "sh" : "cmd";
        String command = String.format("%sexecute.%s %s %s %s", backPath, exeFileSuffix,
                cmd, dbName, filePath);
        log.info("执行命令：{}", command);
        Process process = runtime.exec(command);
        StringBuilder sb = new StringBuilder();

        getLog(process.getInputStream(), sb);
        getLog(process.getErrorStream(), sb);

        InputStreamReader inputStreamReader = new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        String line;
        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line).append("\r\n");
            log.info("--- {} ---", line);
        }

        int res = process.waitFor();
        log.info("执行完成: {}", res);
        if (res != 0){
            throw new CommonException(String.format("执行命令错误 %s", res));
        }

        return sb.toString();
    }

    private void getLog(InputStream inputStream, StringBuilder sb) throws IOException {
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        String line;
        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line);
            log.info("--- {} ---", line);
        }
    }

    private void writeLog(String backPath, String dbName, String fileName, String execLog) throws IOException {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        StackTraceElement last = stackTrace[2];
        String methodName = last.getMethodName();
        String logFilePath = String.format("%slogs%s%s_%s.log", backPath, File.separator, methodName, dbName);

        File logFile = FileUtil.create(logFilePath);

        // 日志文件只保留最近2次操作记录
        OptLog backupLog = OptLog.backup(execLog);
        backupLog.setOptFile(fileName);
        List<OptLog> list = new ArrayList<>();
        list.add(backupLog);

        String historyStr = FileUtils.readFileToString(logFile);
        if (!StringUtils.isEmpty(historyStr)){
            List<OptLog> historyLog = JSON.parseArray(historyStr, OptLog.class);
            int size = historyLog.size();
            int logItems = logItem - 1;
            if (size >= logItems){
                List<OptLog> filterList = historyLog.stream()
                        .sorted(Comparator.comparingLong(OptLog::getTimeLong).reversed())
                        .limit(Math.max(logItems, 1)).collect(Collectors.toList());
                list.addAll(filterList);
            }else if (size > 0){
                list.addAll(historyLog);
            }
        }

        List<String> lines = new ArrayList<>();
        lines.add("[");
        for (int i = 0; i < list.size(); i++) {
            String str = JSON.toJSONString(list.get(i));
            if (i != list.size() - 1){
                str += StringPool.COMMA;
            }
            lines.add(str);
        }
        lines.add("]");

        FileUtils.writeLines(logFile, lines);
        log.info("日志写入 {} -> {}条", list.size(), logFilePath);
    }

    /**
     * 执行还原
     */
    public void restore(OptCommand optCommand) throws IOException {
        String dbName = optCommand.getDbName();
        MultipartFile uploadFile = optCommand.getFile();
        String fileId = optCommand.getFileId();

        if (uploadFile == null && fileId == null){
            throw new CommonException("文件参数缺失");
        }

        String filePath, fileName;
        if (fileId != null){
            filePath = String.format("%sfiles%s%s", backPath,  File.separator, fileId);
            fileName = fileId;
        }else {
            Path tempFile = Files.createTempFile("mysqlTmpImport", ".sql");
            filePath = tempFile.toAbsolutePath().toString();
            File file = tempFile.toFile();
            FileUtils.writeByteArrayToFile(file, uploadFile.getBytes());
            fileName = uploadFile.getOriginalFilename();
        }

        try {
            // 执行备份文件
            log.info("开始还原 {}", filePath);
            String execLog = this.exec(2, dbName, filePath);
            this.writeLog(backPath, dbName, fileName, execLog);

            log.info("还原完成: {}", filePath);
        } catch (Exception e) {
            log.error("还原失败 {}", JSON.toJSONString(optCommand));
            throw new CommonException("还原命令执行失败! ", e);
        }
    }

    public List<BackupFileInfo> getFileList(String dbName) {
        File dir = new File(backPath + "files");
        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        File[] files = dir.listFiles();
        if (files == null || files.length == 0){
            return new ArrayList<>();
        }

        return Arrays.stream(files)
                .filter(f -> {
                    if (!StringUtils.isEmpty(dbName)){
                        String name = f.getName();
                        return name.contains(dbName + ".");
                    }
                    return true;
                }).map(f -> {
                    String name = f.getName();
                    BackupFileInfo info = new BackupFileInfo();
                    info.setName(name);

                    String[] split = name.split("\\.");
                    info.setDbName(split[0]);
                    info.setSize(f.length()/1024);
                    info.setPath(f.getAbsolutePath());
                    info.setLastModified(fmt.format(new Date(f.lastModified())));
                    return info;
                })
                .sorted(Comparator.comparing(BackupFileInfo::getName).reversed())
                .collect(Collectors.toList());
    }

    public void delFile(String fileId) {
        String filePath = String.format("%sfiles%s%s", backPath, File.separator, fileId);
        File file = new File(filePath);
        if (!file.exists()){
            throw new CommonException(String.format("文件[%s]不存在", filePath));
        }
        file.delete();
    }

    public List<OptLog> getOptLogList(Integer type, String dbName) throws IOException {
        String methodName;
        if (type.equals(1)){
            methodName = "backup";
        }else if (type.equals(2)){
            methodName = "restore";
        }else {
            throw new CommonException("指令错误");
        }

        String logFilePath = String.format("%slogs%s%s_%s.log", backPath, File.separator, methodName, dbName);
        File logFile = new File(logFilePath);
        List<OptLog> list = new ArrayList<>();
        if (!logFile.exists()){
            return list;
        }

        String historyStr = FileUtils.readFileToString(logFile);
        if (!StringUtils.isEmpty(historyStr)) {
            List<OptLog> historyLog = JSON.parseArray(historyStr, OptLog.class);
            historyLog.sort(Comparator.comparingLong(OptLog::getTimeLong).reversed());
            list.addAll(historyLog);
        }
        return list;
    }


    public static void main(String[] args) throws IOException, InterruptedException {
//        Runtime runtime = Runtime.getRuntime();
//        Process process = runtime.exec("D:\\data\\ext\\DatabaseOpt\\execute.cmd qq.sql");
//
//        InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8);
//        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//
//        String line;
//        while ((line = bufferedReader.readLine()) != null) {
//            log.info("--- {} ---", line);
//        }
//
//        System.out.println("done." + process.waitFor());

    }

}
